﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Common;

namespace CustomControls
{
    public class SplitterTableLayoutPanel : TableLayoutPanel
    {
        [Flags]
        protected enum SplitType
        {
            None = 0,
            Horizontal = 1,
            Vertical = 2,
            Both = 3
        }

        private bool m_IsSplitterMoving = false;
        private SplitType m_MovingType = SplitType.None;
        private int m_MovingCol = -1;
        private int m_MovingRow = -1;
        private Point m_MovingStartPos = Point.Empty;
        private Point m_LastSplitDrawPos = Point.Empty;

        public bool IsSplitterMoving
        {
            get { return m_IsSplitterMoving; }
            set { m_IsSplitterMoving = value; }
        }

        public SplitterTableLayoutPanel() :
            base()
        {
            SetStyle(ControlStyles.ResizeRedraw, true);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            base.OnPaintBackground(e);

            int[] colWidths = this.GetColumnWidths();
            int[] rowHeights = this.GetRowHeights();

            int cols = colWidths.Length;
            int rows = rowHeights.Length;
            if ((cols == 0) || (rows == 0))
                return;

            int lastCol = cols - 1;
            int lastRow = rows - 1;
            if ((cols > 1) || (rows > 1))
            {
                for (int col = 0; col < cols; col++)
                {
                    for (int row = 0; row < rows; row++)
                    {
                        Control c = this.GetControlFromPosition(col, row);
                        if (c == null)
                            continue;
                        Rectangle rect = c.Bounds;
                        rect.Inflate(2, 2);
                        Border3DSide sides =
                            ((col == 0) ? Border3DSide.Right  : ((col == lastCol) ? Border3DSide.Left : (Border3DSide.Left | Border3DSide.Right))) |
                            ((row == 0) ? Border3DSide.Bottom : ((row == lastRow) ? Border3DSide.Top  : (Border3DSide.Top  | Border3DSide.Bottom)));
                        ControlPaint.DrawBorder3D(e.Graphics, rect, Border3DStyle.Sunken, sides);
                    }
                }
            }
        }

        private IntPtr CreateHalftoneHBRUSH()
        {
            short[] grayPattern = new short[8];
            for (int i = 0; i < 8; i++)
                grayPattern[i] = (short)(0x5555 << (i & 1));
            IntPtr hBitmap = NativeMethods.CreateBitmap(8, 8, 1, 1, grayPattern);

            NativeMethods.LOGBRUSH lb = new NativeMethods.LOGBRUSH();
            lb.lbColor = (uint)ColorTranslator.ToWin32(Color.Black);
            lb.lbStyle = NativeMethods.BrushStyle.BS_PATTERN;
            lb.lbHatch = hBitmap;
            IntPtr brush = NativeMethods.CreateBrushIndirect(ref lb);

            NativeMethods.DeleteObject(hBitmap);
            return brush;
        }

        private Rectangle CalcSplitLine(Point preferredPos, int width, SplitType splitDirection)
        {
            System.Diagnostics.Debug.Assert((splitDirection != SplitType.Both) && (splitDirection != SplitType.None));

            Size size = new Size(this.ClientRectangle.Width, width);
            if (splitDirection == SplitType.Horizontal)
            {
                size = new Size(this.ClientRectangle.Width, width);
                preferredPos.X = 0;
                if (preferredPos.Y < 32)
                    preferredPos.Y = 32;
                if (preferredPos.Y > (this.ClientRectangle.Height - 32))
                    preferredPos.Y = (this.ClientRectangle.Height - 32);
            }
            else
            {
                size = new Size(width, this.ClientRectangle.Width);
                preferredPos.Y = 0;
                if (preferredPos.X < 32)
                    preferredPos.X = 32;
                if (preferredPos.X > (this.ClientRectangle.Width - 32))
                    preferredPos.X = (this.ClientRectangle.Width - 32);
            }


            Rectangle r = new Rectangle(preferredPos, size);
            return r;
        }

        private void DrawSplitHelper(Point preferredPos)
        {
            if ((m_MovingType & SplitType.Horizontal) != SplitType.None)
            {
                Rectangle r = CalcSplitLine(preferredPos, 4, SplitType.Horizontal);
                DrawSplitHelper(r);
            }
            if ((m_MovingType & SplitType.Vertical) != SplitType.None)
            {
                Rectangle r = CalcSplitLine(preferredPos, 4, SplitType.Vertical);
                DrawSplitHelper(r);
            }

            m_LastSplitDrawPos = preferredPos;
        }

        private void DrawSplitHelper(Rectangle r)
        {
            IntPtr parentHandle = this.Handle;
            IntPtr dc = NativeMethods.GetDCEx(parentHandle, IntPtr.Zero, NativeMethods.DeviceContextValues.Cache | NativeMethods.DeviceContextValues.LockWindowUpdate);
            IntPtr halftone = CreateHalftoneHBRUSH();
            IntPtr saveBrush = NativeMethods.SelectObject(dc, halftone);
            NativeMethods.PatBlt(dc, r.X, r.Y, r.Width, r.Height, NativeMethods.TernaryRasterOperations.PATINVERT);
            NativeMethods.SelectObject(dc, saveBrush);
            NativeMethods.DeleteObject(halftone);
            NativeMethods.ReleaseDC(parentHandle, dc);
        }

        protected override void OnLayout(LayoutEventArgs levent)
        {
            if (this.ColumnStyles.Count > 0)
            {
                ColumnStyle oldColumn = this.ColumnStyles[0];
                int newWidth = (int)oldColumn.Width;
                if (newWidth < 32)
                    newWidth = 32;
                if (newWidth > (this.ClientRectangle.Width - 32))
                    newWidth = this.ClientRectangle.Width - 32;
                if (newWidth != (int)oldColumn.Width)
                {
                    ColumnStyle newColumn = new ColumnStyle(SizeType.Absolute, newWidth);
                    this.ColumnStyles[0] = newColumn;
                }
            }

            if (this.RowStyles.Count > 0)
            {
                RowStyle oldRow = this.RowStyles[0];
                int newHeight = (int)oldRow.Height;
                if (newHeight < 32)
                    newHeight = 32;
                if (newHeight > (this.ClientRectangle.Height - 32))
                    newHeight = this.ClientRectangle.Height - 32;
                if (newHeight != (int)oldRow.Height)
                {
                    RowStyle newRow = new RowStyle(SizeType.Absolute, newHeight);
                    this.RowStyles[0] = newRow;
                }
            }

            base.OnLayout(levent);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (e.Clicks == 1)
            {
                int colClicked, rowClicked;
                SplitType splitType = SplitTypeAtPoint(e.Location, out colClicked, out rowClicked);
                if (splitType != SplitType.None)
                {
                    m_IsSplitterMoving = true;
                    m_MovingType = splitType;
                    m_MovingCol = colClicked;
                    m_MovingRow = rowClicked;
                    m_MovingStartPos = e.Location;

                    DrawSplitHelper(e.Location);

                    this.Capture = true;
                }
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);

            if (m_IsSplitterMoving)
            {
                Point currPos = e.Location;
                int deltaX = currPos.X - m_MovingStartPos.X;
                int deltaY = currPos.Y - m_MovingStartPos.Y;

                if ((m_MovingType & SplitType.Vertical) != SplitType.None)
                {
                    ColumnStyle oldColumn = this.ColumnStyles[m_MovingCol];
                    int newWidth = (int)oldColumn.Width + deltaX;
                    if (newWidth < 32)
                        newWidth = 32;
                    if (newWidth > (this.ClientRectangle.Width - 32))
                        newWidth = this.ClientRectangle.Width - 32;
                    ColumnStyle newColumn = new ColumnStyle(SizeType.Absolute, newWidth);
                    this.ColumnStyles[m_MovingCol] = newColumn;

                    //System.Diagnostics.Debug.WriteLine(String.Format("END DRAG - Column Splitter {0} moved from X={1} to X={2} (delta={3}, new width={4}).", m_MovingCol, m_MovingStartPos.X, currPos.X, deltaX, newWidth));
                }
                if ((m_MovingType & SplitType.Horizontal) != SplitType.None)
                {
                    RowStyle oldRow = this.RowStyles[m_MovingRow];
                    int newHeight = (int)oldRow.Height + deltaY;
                    if (newHeight < 32)
                        newHeight = 32;
                    if (newHeight > (this.ClientRectangle.Height - 32))
                        newHeight = this.ClientRectangle.Height - 32;
                    RowStyle newRow = new RowStyle(SizeType.Absolute, newHeight);
                    this.RowStyles[m_MovingRow] = newRow;

                    //System.Diagnostics.Debug.WriteLine(String.Format("END DRAG - Row Splitter {0} moved from Y={1} to Y={2} (delta={3}, new height={4}).", m_MovingRow, m_MovingStartPos.Y, currPos.Y, deltaY, newHeight));
                }

                //
                m_IsSplitterMoving = false;
                m_MovingType = SplitType.None;
                m_MovingCol = -1;
                m_MovingRow = -1;
                m_MovingStartPos = Point.Empty;

                DrawSplitHelper(m_LastSplitDrawPos);

                this.Capture = false;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (m_IsSplitterMoving)
            {
                Point currPos = e.Location;
                int deltaX = currPos.X - m_MovingStartPos.X;
                int deltaY = currPos.Y - m_MovingStartPos.Y;

                if ((m_MovingType & SplitType.Vertical) != SplitType.None)
                {
                    //System.Diagnostics.Debug.WriteLine(String.Format("Column Splitter {0} moved from X={1} to X={2} (delta={3}).", m_MovingCol, m_MovingStartPos.X, currPos.X, deltaX));
                }
                if ((m_MovingType & SplitType.Horizontal) != SplitType.None)
                {
                    //System.Diagnostics.Debug.WriteLine(String.Format("Row Splitter {0} moved from Y={1} to Y={2} (delta={3}).", m_MovingRow, m_MovingStartPos.Y, currPos.Y, deltaY));
                }

                DrawSplitHelper(m_LastSplitDrawPos);
                DrawSplitHelper(e.Location);
            }
            else
            {
                SplitType splitType = SplitTypeAtPoint(e.Location);
                if (splitType == SplitType.None)
                    this.Cursor = Cursors.Default;
                else if (splitType == SplitType.Both)
                    this.Cursor = Cursors.SizeAll;
                else if (splitType == SplitType.Horizontal)
                    this.Cursor = Cursors.SizeNS;
                else if (splitType == SplitType.Vertical)
                    this.Cursor = Cursors.SizeWE;
            }
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);

            this.Cursor = Cursors.Default;
        }

        protected SplitType SplitTypeAtPoint(Point pt)
        {
            int colTmp, rowTmp;
            return SplitTypeAtPoint(pt, out colTmp, out rowTmp);
        }

        protected SplitType SplitTypeAtPoint(Point pt, out int colSplitterIdx, out int rowSplitterIdx)
        {
            SplitType splitType = SplitType.None;
            colSplitterIdx = -1;
            rowSplitterIdx = -1;

            if (ClientRectangle.Contains(pt))
            {
                int[] colWidths = this.GetColumnWidths();
                int[] rowHeights = this.GetRowHeights();

                int cols = colWidths.Length;
                int rows = rowHeights.Length;
                if ((cols > 0) && (rows > 0))
                {
                    int colSplitters = cols - 1;
                    for (int col = 0; col < colSplitters; col++)
                    {
                        Control cTL = this.GetControlFromPosition(col + 0, 0);
                        Control cBR = this.GetControlFromPosition(col + 1, rows-1);
                        if ((cTL == null) || (cBR == null))
                            continue;

                        Rectangle r = new Rectangle(cTL.Bounds.Right, cTL.Bounds.Top, cBR.Bounds.Left - cTL.Bounds.Right, cBR.Bounds.Bottom - cTL.Bounds.Top);
                        if (r.Contains(pt))
                        {
                            splitType |= SplitType.Vertical;
                            colSplitterIdx = col;
                            break;
                        }
                    }

                    int rowSplitters = rows - 1;
                    for (int row = 0; row < rowSplitters; row++)
                    {
                        Control cTL = this.GetControlFromPosition(0,        row + 0);
                        Control cBR = this.GetControlFromPosition(cols - 1, row + 1);
                        if ((cTL == null) || (cBR == null))
                            continue;

                        Rectangle r = new Rectangle(cTL.Bounds.Left, cTL.Bounds.Bottom, cBR.Bounds.Right - cTL.Bounds.Left, cBR.Bounds.Top - cTL.Bounds.Bottom);
                        if (r.Contains(pt))
                        {
                            splitType |= SplitType.Horizontal;
                            rowSplitterIdx = row;
                            break;
                        }
                    }
                }
            }

            return splitType;
        }
    }
}
